/*
    node 的 mySql  使用 (数据库)

    速查表
    https://www.runoob.com/nodejs/nodejs-express-framework.html   //Express
    https://www.runoob.com/nodejs/nodejs-mysql.html   //连接 MySQL
    https://www.runoob.com/sql/sql-tutorial.html    //写SQL

    开发需求：
    1、快速生成对应所有数据库表的基础API，API名等于表名，实现直接API增删改查表
    2、特殊API按功能开发，如登陆、上传、富文本编辑器
*/
// http://localhost/api/v1/

//建单表基准sql
// CREATE TABLE `active` (
//   `id` TINYINT(4) NOT NULL AUTO_INCREMENT,
//   `title` VARCHAR(200) DEFAULT NULL,
//   `date` VARCHAR(50) DEFAULT NULL,
//   `value` TEXT,
//   `img` VARCHAR(200) DEFAULT NULL,
//   `sort` INT(5) DEFAULT NULL,
//   `issue` INT(5) DEFAULT NULL,
//   `top` INT(5) DEFAULT NULL,
//   PRIMARY KEY (`id`)
// ) ENGINE=MYISAM AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;


//Web应用框架
const express = require('express') 
const app = express() //实例化express应用
app.use('/public', express.static('public')) // 设置图片、视频、附件等静态资源存放路径
const bodyParser = require('body-parser') // post中间件，用于post解析参数
// 处理post请求数据解析
app.use(bodyParser.json()) // JSON请求
app.use(bodyParser.urlencoded({extended: false})) // 表单请求

const fs = require('fs') //文件系统模块

const jwt  = require('jsonwebtoken'); //生成token值

// 在处理模块中引入第三方解析模块
const multipart = require('connect-multiparty') 
const multipartMiddleware = multipart() // post数据解析

// cors跨域支持
const cors = require('cors') 
app.use(cors()) // 解决跨域


const mysql = require('mysql')
/* 配置数据库*/
const sqlConfig = { 
  host: 'localhost', // 主机地址
  user: 'root',
  password: '@kingdom10',
  database: 'www_book_com', // 数据库名
  port:3306
}
//创建连接数据池
const pool = mysql.createPool(sqlConfig); 

//封装mysql请求
let query = function( sql, values ) {
  // 返回一个 Promise
  return new Promise(( resolve, reject ) => {
    pool.getConnection(function(err, connection) {
      if (err) {
        reject( err )
      } else {
        connection.query(sql, values, ( err, rows) => {
          if ( err ) {
            reject( err )
          } else {
            resolve( rows )
          }
          // 结束会话
          connection.release()
        })
      }
    })
  })
}

// API版本号
const versions = '/v1/'
// (数据池)存储所有数据库表数据，减少对数据库的访问
const dataArray = []
//用于存储数据库表名，同时同名API增删改查，实现API同名操作数据库表
let apiArray = [] //['tablename','tablename'] 

//读取数据库所有表名
async function initTable(){
   const data = await query("SELECT table_name FROM information_schema.tables WHERE table_schema='" + sqlConfig.database + "'");    
   apiArray = JSON.parse(JSON.stringify(data)).map(function(value){
      return value.table_name
   })   
}
initTable().then(function(){ //初始化表回调
  // 批量生成增删改查API
  apiArray.forEach((item) => {
    tableDataUpdate(item) // 初始化数据池
    getAPI(item) //查
    deleteAPI(item) // 删
    if (item != 'login') { // 登录用户数据单独处理
      putAPI(item) // 改
      addAPI(item) // 增
    }  
  })
  /* 列名查询 */
  apiArray.forEach(function (item) {
    app.get(versions + item + 'Head',async function (req, res) { // 建立数据接口
      const rows = await query('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME="' + item + '"')
      res.json(rows)      
    })
  })
})

// 排序数据
function sortData(rows){
  return JSON.parse(JSON.stringify(rows)).sort((a, b) => {
      a = new Date(a.date).getTime()
      b = new Date(b.date).getTime()
      return b - a
    }).sort((a, b) => {
      return b.sort - a.sort
    }).sort((a, b) => { //勾选TOP优先级最高
      return b.top - a.top
    })    
}
/* 根据表名(对应API名)更新数据池（查询数据库） */
async function tableDataUpdate(item) {
  const rows = await query('SELECT * FROM ' + item)
  let data = sortData(rows);//排序数据
  return dataArray[apiArray.indexOf(item)] = data //按数据库表名数组索引记录数据
}

//格式化请求参数value值，使其符合sql查询字段规范
function ObjectToSqlString(da){
  for(let key in da){     
    if(da[key] && da[key].constructor.toString().indexOf('Array') > 0){ //给sql的Array值转字符串并加''号
      da[key] = '\'' + da[key].toString() + '\''
    }else if(typeof da[key] === 'boolean'){//布尔值转存0、1
      da[key] = Number(da[key])
    }else if(typeof da[key] === 'string'){ //给sql的string值加''号
      da[key] = '\'' + da[key] + '\''
    }
  }
  return da
} 

/* 修改内容数据接口*/
function putAPI(item) {
  app.put(versions + item,async function (req, res) { // 建立数据接口
    let da = req.body // 请求体 数据案例 { top: false, id: [ 18, 16 ] }  || { top: false, id: 16 }
    const idUl = Array.isArray(da.id) ? da.id.join(",") : da.id //多个ID则用','合并ID
    let te = '';
    da = ObjectToSqlString(da); //格式化请求参数value值
    for(let key in da){    
      if(key === 'id') continue; //ID不参与拼接   
      te += "," + key + "=" + da[key] //拼接sql查询字段
    }
    te = te.slice(1) //去掉第一个','
    //UPDATE websites SET issue=1  WHERE id IN (19,15,16)  批量更新sql
    //UPDATE floor SET title="L21",sort=10,issue=1,top=0,date="2021-06-23 17:36:52" WHERE id IN (18) 多字段更新
    console.log('UPDATE ' + item + ' SET ' + te + ' WHERE id IN (' + idUl + ")")
    await query('UPDATE ' + item + ' SET ' + te + ' WHERE id IN (' + idUl + ")")
    if(item === 'roles'){ //roles表关联login表修改用户数据
      alterUserData('login',da)
    }
    tableDataUpdate(item) // 刷新单个数据池
    res.status(200)
    res.json()
  })
}

/* 插入数据接口*/
function addAPI(item) {
  app.post(versions + item,async function (req, res) { // 建立数据接口
    let da = req.body // 请求体
    let te = '' //查询字段
    let valTe = '' //对应值
    da = ObjectToSqlString(da); //格式化请求参数value值
    for (let key in da) { // sql字段拼接
      if(key === 'id') continue; //ID不参与拼接(ID在数据库设置自增长)
      te += ',' + key //拼接key
      valTe += ',' + da[key] //拼接value
    }
    te = te.slice(1)
    valTe = valTe.slice(1) //去掉第一个','

    // 'INSERT INTO oplog(username,text,date)  VALUES('admin','新增内容：主力店（成功）','2021-11-05 11:03:50')'
    console.log('INSERT INTO ' + item + '(' + te + ')  VALUES(' + valTe + ')')
    const result = await query('INSERT INTO ' + item + '(' + te + ')  VALUES(' + valTe + ')')
    if(item === 'roles'){ //roles表关联login表添加用户数据
      da.rid = JSON.parse(JSON.stringify(result)).insertId //记录自增ID
      addUserData('login',da)
    }
    tableDataUpdate(item) // 刷新单个数据池
    res.status(200)
    res.json() 
    
  })
}

// 数据删除接口
function deleteAPI(item) {
  app.delete(versions + item,async function (req, res) { // 建立数据接口
    const da = req.query // 请求数据
    console.log(req)
    // DELETE FROM websites where id=6
    console.log('DELETE FROM ' + item + ' WHERE id= ' + da.id)
    const result = await query('DELETE FROM ' + item + ' WHERE id= ' + da.id)
    if(item === 'roles'){ //roles表关联login表删除用户数据
      da.rid = JSON.parse(JSON.stringify(result)).insertId //记录自增ID
      deleteUserData('login',da)
    }
    tableDataUpdate(item) // 刷新单个数据池
    res.status(200)
    res.json() 
  })
}

//所有数组字符串(原因：MYSQL以字符串形式存放数组)
const arrayValue = ['child','child3','images','images2','images3'];
/* 数组字符串转数组 */
function StringToArray(data){
  data.forEach(function(item){ 
    for(let key in item){
      if(arrayValue.includes(key) && item[key] && typeof item[key] === 'string'){                  
        item[key] = item[key].split(",")
      }
    }  
  })
  return data
}

/* 
筛选发布数据 通过参数 all=1 可以获取全部数据（包括待发布数据）（用于中台数据显示）
d_list?all=1
*/
function issueFilter(data,req){
  if(data[0] && data[0].issue && req.query && req.query.all != 1){ 
    data = data.filter(function(item){  //前台只显示发布数据
      return parseInt(item.issue) === 1;
    })
    return data
  }
}

/*
筛选分类选项,通过参数classfilter="音乐"对数据进行分类筛选
d_list?classfilter="音乐"
*/
function classFilter(data,req){  
  if(data && data[0] && data[0].child && req.query && req.query.classfilter){ 
    const className = decodeURI(req.query.classfilter) //中文解码    
    data = data.filter(function(item){  //前台只显示发布数据  
      return item.child.includes(className) 
    })    
    return data
  }
}

//返回当前时间
function thisDate() { 
    const date = new Date();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0') //使用ES6 padStart(2,'0') 为日期强制两位数，少于两位数前面加0
    return date.getFullYear() + "-" + month + "-" + day + " " + date
        .getHours().toString().padStart(2, '0') + ":" + date.getMinutes().toString().padStart(2, '0') +
        ":" + date
        .getSeconds().toString().padStart(2, '0');
}

/*
搜索筛选,通过参数seek="关键字"对数据进行分类筛选
d_list?seek="关键字"
*/
function seekFilter(data,req){  
  if(data && data[0] && data[0].child && req.query && req.query.seek){ 
    const className = decodeURI(req.query.seek) //中文解码    
    //console.log(className)
    data = data.filter(function(item){  //前台只显示发布数据  
      return JSON.stringify(item).includes(className) 
    })       
    //如果值非空，则记录搜索关键字
    if(className){      
      console.log(className)
      query('INSERT INTO d_history(title,date) VALUES("' + className + '","' + thisDate() + '")')    
      tableDataUpdate('d_history') // 刷新单个数据池
    }    
    return data
  }
}

/*  
  数据分页（pagenum:当前页码，pagesize:每页条数，默认传这两个参数，进行数据分页）
  对返回数据加子数据 list(指定页码分页数据)、sum（数据总条数) 
  d_list?pagenum=0&&pagesize=6`
*/
function paging(data,req){
  if(req.query && req.query.pagenum && req.query.pagesize){
      const pagenum = req.query.pagenum;
      const pagesize = req.query.pagesize
      const da = {}        
      let pageData = [];
      da.sum = data.length; //sum（数据总条数） 
      for(var i = 0;i < Math.ceil(data.length/pagesize);i++){ //数据拆分
        pageData.push(data.slice(i*pagesize,(i+1)*pagesize))
      } 
      da.list = pageData[pagenum];//list（指定页码分页数据）          
      return da
  }
}

/*
访问指定ID数据与关联指定个数数据
d_list?id=1&&pagesize=6`
*/
function idFilter(data,req){
    if(req.query && req.query.id){ //指定ID访问数据
      const reqId = parseInt(req.query.id)
      let da = {};
      da = data.find((item)=>{
        return item.id === reqId
      })
      if(data && req.query.pagesize){ //指定ID，返回多条相连数据
        const halfSize = Math.floor(req.query.pagesize/2);//请求条数的一半
        const n = data.findIndex((item)=>{ //指定ID索引
          return item.id === reqId
        })
        const prevData = data.slice(n-halfSize,n) //指定ID前面数据
        const nextData = data.slice(n+1,n+1+(req.query.pagesize - prevData.length))  //指定ID后面数据 
        da.list = prevData.concat(nextData); //相连数据
        da.list = StringToArray(da.list) // 数组字符串转数组
      }
      return da
    }  
}    
    
// 常规get数据接口
function getAPI(item){
  app.get(versions + item, function (req, res) { // 建立数据接口

    let apiTableData = dataArray[apiArray.indexOf(item)]; //请求API全部数据库数据
    //这里用了深拷贝，以隔绝对数据库数据的影响
    let data = JSON.parse(JSON.stringify(apiTableData));//请求API返回的数据    
        
    // 数组字符串转数组 
    data = StringToArray(data)

    //筛选发布数据
    data = issueFilter(data,req) || data
    //分类筛选
    data = classFilter(data,req) || data
    //关键字搜索筛选与记录历史
    data = seekFilter(data,req) || data

    //数据分页
    data = paging(data,req) || data
    
    //访问指定ID数据与关联指定个数数据 
    data = idFilter(data,req) || data
    
    //响应数据
    let resData = {
      meta:{
        status:200,
        message:'成功获取数据'
      },
      data:data
    }
    //较验tokey值    
    // if(!verson(req)){      
    //   resData = {
    //     meta:{
    //       status: 400,
    //       message: '无效的tokey'
    //     },
    //     data:''
    //   }
    //   console.log('无效的tokey')
    // } 
    //console.log(data)
    res.status(200)
    res.json(resData) // 响应头返回相应查询数据
  })
}


/* 角色表需要关联操作登陆表用户数据 */
/* 添加用户数据 */
async function addUserData(tableName,da){
  query('INSERT INTO ' + tableName + '(username,password,date,rid) VALUES(' + da.username + ',' + da.password + ',' + da.date + ',' + da.rid + ')')
  tableDataUpdate(tableName) // 刷新单个数据池
}
/* 修改用户数据 */
async function alterUserData(tableName,da){
  await query('UPDATE ' + tableName + ' SET username=' + da.username + ',password=' + da.password + ',date=' + da.date + ' WHERE rid IN (' + da.id + ')')
  tableDataUpdate(tableName) // 刷新单个数据池
}
/* 删除用户数据 */
async function deleteUserData(tableName,da){
  await query('DELETE FROM ' + tableName + ' WHERE rid= ' + da.id)
  tableDataUpdate(tableName) // 刷新单个数据池
}
/* 修改密码(表名，请求头数据对象) */
async function alterPassword(tableName,da){
  await query('UPDATE ' + tableName + ' SET password=' + da.setPassword + ' WHERE username IN (' + da.username + ')')
  tableDataUpdate(tableName) // 刷新单个数据池
}


// 在systemData.js读取系统信息并module.exports系统信息对象（拆分js）
const systemInfo = new(require('./systemData'))()
app.get(versions + 'system', function (req, res) { //返回系统信息
  res.status(200),
    res.json(systemInfo)
})

//返回响应头数据（请求头数据）
function returnResData(reqBody){
  const logDa = dataArray[apiArray.indexOf('login')] // 数据库用户数据
  const resData = {} // 响应数据
  const logAr = [
    [], // 记录登录成功与否（1：成功，0：失败）
    [], // 记录用户名是否存在（1：存在，0：不存在）
    [] // 记录密码是否正确（1：正确,0:错误）
  ]
  
  logDa.forEach(function (item) {
    //记录是否登录成功
    if(reqBody.username == item.username && reqBody.password == item.password){
      logAr[0].push(1)
      //添加登录成功返回头数据
      resData.data = {
        id:item.id,
        rid:item.rid,
        username:item.username
      }      
    }else{
      logAr[0].push(0)
    }     
    //记录用户名是否存在 
    if (reqBody.username == item.username) {
      logAr[1].push(1)
      // 记录密码是否正确
      reqBody.password == item.password ? logAr[2].push(1) : logAr[2].push(0)              
    } else {
      logAr[1].push(0)
    }
  })
  if (logAr[0].includes(1)) {
    resData.meta = {
      'status': 200,
      'message': '用户登录成功'
    }    
  } else if (!logAr[1].includes(1)) {
    resData.meta = {
      'status': 400,
      'message': '用户名不存在'
    }
  } else if (!logAr[2].includes(1)) {
    resData.meta = {
      'status': 400,
      'message': '密码错误'
    }
  }
  return resData //返回响应头数据
}

//返回加密tokey值
function returnTokey(req){
  let payload = req && req.body;
  return jwt.sign(payload,'KINGDOM',{expiresIn:60*60*24});// 授权时效1分钟
}

/**
 * 校验token是否过期
 * */
function verson(req){
    var token=req.headers.token;
    let con = jwt.verify(token, 'KINGDOM', (err, decoded) => {
        if (err) {
            //console.log(err);
            if(err.name == 'TokenExpiredError'){//token过期                
                return 'token过期';
            }else if(err.name == 'JsonWebTokenError'){//无效的token               
                return '无效的token';
            }
        }else{
            return decoded;
        }
    }) 
    console.log(con);
    if(con.iat<con.exp){
        return true //开始时间小于结束时间，代表token还有效
    }else{
        return false
    }
}


//记录登陆日志
function recordLogin(req,resData){
  const name = req.body.username //请求用户名
  const ip = req && req.socket && req.socket.remoteAddress && req.socket.remoteAddress.split(":")[3] || '192.168.18.186' //请示IP
  let state = 0; //登陆状态
  //登陆时间
  const de = new Date() 
  const date = de.toLocaleDateString() + " " + de.getHours() + ":" + de.getMinutes() + ":" + de.getSeconds();  
  //为登陆成功响应数据返回token值  
  if(resData.meta.status == 200){   
    state = 1;
  }    
  console.log('INSERT INTO loginfo(name,ip,state,date)  VALUES("' + name + '","'+ ip +'",'+ state +',"'+ date +'")')
  query('INSERT INTO loginfo(name,ip,state,date)  VALUES("' + name + '","'+ ip +'",'+ state +',"'+ date +'")')
  tableDataUpdate('loginfo') // 刷新单个数据池
}

// 用户登录数据接口
app.post(versions + 'login', function (req, res) { // 建立数据接口
  // req.params.id(get请求参数 /:id)  req.query.id(get请求 ?id=0)   req.body.id(post请求体)  
  let resData = {} // 响应数据 
  resData = returnResData(req.body); //返回响应数据

  //登陆成功操作
  if(resData.meta.status == 200){ 
    resData.data.token = returnTokey(req) //为响应数据加tokey值  
  } 
  
  //记录登陆日志
  recordLogin(req,resData)

  res.json(resData) // 以json形式发送响应数据
})

// 修改密码数据接口
app.put(versions + 'login', function (req, res) { // 建立数据接口
  let reqBody = req.body // post请求数据
  let resData = {} // 响应数据  
  resData = returnResData(reqBody); //返回响应数据
  if(resData.meta.status === 200){//为成功响应执行修改密码操作
    resData.meta.message = "密码修改成功"
    reqBody = ObjectToSqlString(reqBody); //格式化请求数据
    alterPassword('login',reqBody);//修改登陆密码
    alterPassword('roles',reqBody) //关联修改角色表用户密码
    tableDataUpdate('login') // 刷新数据池单个数据
  }
  res.json(resData) // 以json形式发送响应数据
})

/* 文件上传 */
let response
app.post(versions + 'file_upload', multipartMiddleware, function (req, res) { // 文件上传
  //console.log(req.files.file) // 上传文件信息
  const r = new Date().getTime(); // 定文件唯一路径
  const des_file = '/serves/images/' + r + req.files.file.originalFilename // 文件存放相对路径
  fs.readFile(req.files.file.path, function (error, data) {
    fs.writeFile(__dirname + des_file, data, function (error) { // _dirname （写入需绝对路径，把相对路径转换成绝对路径）
      if (error) throw error;
      response = {
        message: '文件上传成功',
        data: des_file
      }      
      //console.log(response)
      res.json(response)
    })
  })
})

/* neditor编辑器 */
let buf = new Buffer.alloc(3000)
fs.open(__dirname + '/ueConfig.json', 'r+', function (error, fd) { // 打开ueditor配置文件
  if (error) throw error;
  // console.log("配置文件打开成功");
  fs.read(fd, buf, 0, buf.length, 0, function (error, bytes) { // 读取配置
    if (error) throw error;
    // console.log(bytes + "  字节被读取");
    app.get(versions + 'ueditor', function (req, res) { // 文件上传
      const daOb = JSON.parse(buf.slice(0, bytes).toString())
      res.jsonp(daOb) // 传送JSONP响应
    })
  })
})

// 编辑器文件上传
app.post(versions + 'ueditor', multipartMiddleware, function (req, res) { 
  // const action = req.query.action;
  // if (action == 'uploadimage' || action == 'uploadvideo' || action == 'uploadfile') { //图片/视频/附件上传判断
  //console.log(req.is())
  //console.log(req.files.upfile)
  const r = new Date().getTime() + '_' // 定文件唯一路径
  const des_file = '/serves/images/' + r + req.files.upfile.originalFilename // 文件存放相对路径
  fs.readFile(req.files.upfile.path, function (error, data) {
    fs.writeFile(__dirname + des_file, data, function (error) { // _dirname （写入需绝对路径，把相对路径转换成绝对路径）
      if (error) throw error;
      let responseImg = { //要按neditor后端请求规范返回响应数据
        code: '200',
        message: 'SUCCESS',
        url: des_file,
        thumbnail: des_file,
        title: req.files.upfile.originalFilename,
        original: req.files.upfile.originalFilename,
        error: error
      }            
      res.jsonp(responseImg)
    })
  })
})

// conn.end(); //关闭数据库

//统一的异常处理，防止程序崩溃
process.on('uncaughtException', function (err) { 
  //打印出错误
  console.log(err) 
});


// 配置服务端口
app.listen(2108, () => {
  console.log('server :localhost:2108/v1/')
})